package websocket

import (
	"errors"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"sync"
	"time"
)

// webSocket 连接
type Connection struct {
	conn                      *websocket.Conn // 管理连接
	message                   chan []byte     // 消息通道
	isMessageChanelClose      chan struct{}   // 消息缓冲通道
	group                     *GroupConn      // 所属群组
	pongHandler               func(message string) error
	messageBeforeWriteHandler func(messageType int, message *[]byte) // 消息发送之前对消息进行自定义处理函数
	messageAfterReadHandler   func(messageType int, message *[]byte) // 读取消息之后对消息的处理函数，若连接处于群组状态，且不希望消息被发送出去，可将消息置 nil
	unexpectedErrorHandler    func(error)
}

type Conn interface {
	SendMessage(messageType int,message []byte) error
	JoinGroup(group *GroupConn) error
	CloseChanel()
	SetMessageBeforeWriteHandler(h func(messageType int, message *[]byte))
	SetMessageAfterReadHandler(h func(messageType int, message *[]byte))
	SetCloseHandler(h func(code int, text string) error)
	SetUnexpectedErrorHandler(h func(err error))
}

var MessageChanelCloseError = errors.New("message chanel has been closed")

func (conn *Connection) ReadMessage() {
	defer func() {
		conn.conn.Close()
	}()

	for true {
		messageType, message, err := conn.conn.ReadMessage()
		if err != nil {
			// 若错误不是期待中的则执行
			if websocket.IsUnexpectedCloseError(
				err,
				websocket.CloseGoingAway,
				websocket.CloseAbnormalClosure) {

				// 不是逾期错误不处理，丢给用户处理
				if conn.unexpectedErrorHandler != nil {
					conn.unexpectedErrorHandler(err)
				}

			}
			break
		}

		if conn.messageAfterReadHandler != nil {
			conn.messageAfterReadHandler(messageType, &message)
		}

		if message != nil && conn.group != nil {
			// 将消息放进群组消息队列之中
			conn.group.ReceiveMessage(messageType,message)
		} else if message != nil {
			conn.SendMessage(messageType,message)
		}
	}
}

func (conn *Connection) WriteMessage() {
	ticker := time.NewTicker(time.Duration(configure.PingPeriodTime) * time.Millisecond)
	defer func() {
		ticker.Stop()
		conn.conn.Close()
	}()

	for true {
		select {
		case message, ok := <-conn.message:
			conn.conn.SetWriteDeadline(time.Now().Add(time.Duration(configure.WriteWaitTime)))
			if !ok {
				conn.conn.WriteMessage(websocket.CloseMessage, nil)
				return
			}

			w, err := conn.conn.NextWriter(configure.MessageType)
			if err != nil {
				log.Println(err)
				return
			}

			if conn.messageBeforeWriteHandler != nil {
				conn.messageBeforeWriteHandler(configure.MessageType, &message)
			}
			w.Write(message)

			if err := w.Close(); err != nil {
				log.Println(err)
				return
			}
		case <-ticker.C:
			conn.conn.SetWriteDeadline(time.Now().Add(time.Duration(configure.WriteWaitTime)))
			if err := conn.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				log.Println(err)
				return
			}
		}
	}
}



func (conn *Connection) ReceiveMessage(message []byte) error {
	select {
	case <-conn.isMessageChanelClose:
		return MessageChanelCloseError
	default:
		conn.message <- message
		return nil
	}
}


func (conn *Connection) SendMessage(messageType int,message []byte) error {
	if messageType == websocket.TextMessage || messageType == websocket.BinaryMessage {
		return conn.ReceiveMessage(message)
	}
	return conn.conn.WriteMessage(messageType,message)
}

func NewConnection(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (Conn, error) {

	connTmp, err := newConn(configure.ReadBufferSize, configure.WriteBufferSize, w, r, responseHeader)
	if err != nil {
		return nil, err
	}

	conn := GetConn()
	conn.conn = connTmp
	conn.conn.SetReadLimit(configure.MaxMessageSize)
	conn.group = nil
	conn.message = make(chan []byte, configure.ReadBufferSize)
	conn.isMessageChanelClose = make(chan struct{})
	conn.conn.SetPongHandler(conn.pongHandler)
	go conn.ReadMessage()
	go conn.WriteMessage()
	return conn, nil
}

func ReleaseConnection(conn *Connection) {
	ReleaseConn(conn)
}

func (conn *Connection) SetWriteDeadline(t time.Time) error {
	return conn.conn.SetWriteDeadline(t)
}

func (conn *Connection) SetPongHandler(h func(AppData string) error) error {
	if h == nil {
		return errors.New("param::h can not be nil")
	}
	conn.pongHandler = h
	return nil
}

func (conn *Connection) SetMessageBeforeWriteHandler(h func(messageType int, message *[]byte)) {
	conn.messageBeforeWriteHandler = h
}

func (conn *Connection) SetMessageAfterReadHandler(h func(messageType int, message *[]byte)) {
	conn.messageAfterReadHandler = h
}

func (conn *Connection) SetCloseHandler(h func(code int, text string) error) {
	conn.conn.SetCloseHandler(h)
}

func (conn *Connection) SetUnexpectedErrorHandler(h func(err error)) {
	conn.unexpectedErrorHandler = h
}

//  群连接
func (conn *Connection) JoinGroup(group *GroupConn) error {
	if group == nil {
		return errors.New("param::h is nil")
	}

	if conn.group != nil {
		return errors.New("can not join into multi group")
	}

	conn.group = group
	return nil
}

func (conn *Connection) GetPongHandler() func(AppData string) error {
	if conn.pongHandler != nil {
		return conn.pongHandler
	}

	// 默认实现心跳包
	return func(AppData string) error {
		conn.conn.SetReadDeadline(time.Now().Add(time.Duration(configure.PongWaitTime) * time.Millisecond))
		return nil
	}
}

func (conn *Connection) CloseChanel() {
	close(conn.message)
	close(conn.isMessageChanelClose)
}

// 对象池
var upGraderPool *sync.Pool
var connPool *sync.Pool

func init() {
	upGraderPool = &sync.Pool{
		New: func() interface{} {
			return &websocket.Upgrader{}
		},
	}

	connPool = &sync.Pool{
		New: func() interface{} {
			return &Connection{}
		},
	}
}

func GetUpGrader(readBufferSize, writeBufferSize int) *websocket.Upgrader {
	tmp := upGraderPool.Get().(*websocket.Upgrader)
	tmp.WriteBufferSize = writeBufferSize
	tmp.ReadBufferSize = readBufferSize
	return tmp
}

func ReleaseUpGrader(tmp *websocket.Upgrader) {
	if tmp != nil {
		upGraderPool.Put(tmp)
	}
}

func newConn(readBufferSize, writeBufferSize int, w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error) {

	tmp := GetUpGrader(readBufferSize, writeBufferSize)
	defer ReleaseUpGrader(tmp)

	conn, err := tmp.Upgrade(w, r, responseHeader)
	if err != nil {
		return nil, err
	}

	return conn, nil
}

func GetConn() *Connection {
	return connPool.Get().(*Connection)
}

func ReleaseConn(conn *Connection) {
	if conn != nil {
		connPool.Put(conn)
	}
}
